﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SvcLog
{
    public class SvcLog
    {
        public static void Initialize(string logDir = "logs",int maxLogs=16)
        {
            SvcLog.logDir = logDir;
            SvcLog.maxLogCount = maxLogs;
            hookConsoleOut();
        }

        private static ForkTextWriter currentWriter = null;

        private static void hookConsoleOut()
        {
            try
            {
                initializeLogs();
            }catch (Exception ex)
            {
                
                Console.WriteLine(ex.ToString());
                Console.WriteLine("Error initializing Logs, using default");
                return;
            }
            TextWriter baseStream = Console.Out;
            StreamWriter logWriter = tryOpenLogFile();
            currentWriter = new ForkTextWriter(baseStream, logWriter);
            Console.SetOut(currentWriter);
            Console.SetError(currentWriter);
            lastDays = DateStamp;
            currentWriter.OnOutput += CurrentWriter_OnOutput;

        }

        private static int DateStamp
        {
            get
            {
                return DateTime.Now.DayOfYear;
            }
        }

        private static int lastDays = -1;

        private static void CurrentWriter_OnOutput(object sender, EventArgs e)
        {
            if (lastDays != DateStamp)
            {
                lastDays = DateStamp;
                try
                {
                    initializeLogs();
                }
                catch (Exception ex)
                {

                    Console.WriteLine(ex.ToString());
                    Console.WriteLine("Error initializing Logs, using default");
                    return;
                }
                currentWriter.ChangeTextWriter2(tryOpenLogFile());
            }
        }

        private static StreamWriter tryOpenLogFile()
        {
            StreamWriter sw = null;
            string fileAppend = "";
            string fileName = tryGetLogName();
            int retry = 10;
            while (true) {
                try
                {
                    sw = File.AppendText(Path.Combine(logDir, fileName+fileAppend));
                    break;
                }
                catch {
                    fileAppend = fileAppend + "_";
                    retry--;
                    if(retry < 0)
                    {
                        sw = StreamWriter.Null;
                        break;
                    }
                }
            }
            return sw;
        }

        private static string tryGetLogName()
        {
            string logName = Process.GetCurrentProcess().ProcessName+"_" + DateTime.Now.ToString("yyyy\\_MM\\_dd\\_HH\\_mm\\_ss\\_fff") + ".log";
            if (getLogFilename != null)
            {
                try
                {

                    logName = getLogFilename() + ".log";
                    foreach (var item in Path.GetInvalidFileNameChars())
                    {
                        logName = logName.Replace(item, '_');
                    }
                }
                catch (Exception)
                {
                    Console.WriteLine("Error initializing Logger: When invoking GetLogFilenameDelegate(), using default.");
                }
            }
            return logName;
        }

        /// <summary>
        /// 最大日志文件数量
        /// </summary>
        private static int maxLogCount = 100;
        private static string logDir = "logs";
        static void initializeLogs()
        {
            if (File.Exists(logDir))
            {
                throw new Exception();
            }
            if (!Directory.Exists(logDir))
            {
                Directory.CreateDirectory(logDir);
            }
            //删除过期日志
            FileInfo[] files = Directory.GetFiles(logDir, "*.log").Select(f => new FileInfo(f)).ToArray();
            files.OrderByDescending(f => f.LastWriteTime).Skip(maxLogCount - 1).ToList().ForEach(f => { try { f.Delete(); } catch { } });
        }
        /// <summary>
        /// 用于获取日志文件名的委托
        /// </summary>
        /// <returns>日志文件名，不包含后缀。</returns>
        public delegate string GetLogFilenameDelegate();
        private static GetLogFilenameDelegate getLogFilename;
        /// <summary>
        /// 设置文件名的格式，例如
        /// <code>BizLog.SetLogFilenameGenerator(()=>DateTime.Now.ToString());</code>
        /// </summary>
        /// <param name="a"></param>
        public static void SetLogFilenameGenenrator(GetLogFilenameDelegate a)
        {
            getLogFilename = a;   
        }
    }

    class ForkTextWriter : System.IO.TextWriter
    {
        public event EventHandler<EventArgs> OnOutput;
        private TextWriter w1, w2;

        public void ChangeTextWriter1(TextWriter w)
        {
            w1?.Dispose();
            w1 = w;
        }
        public void ChangeTextWriter2(TextWriter w)
        {
            w2?.Dispose();
            w2 = w;
        }
        public ForkTextWriter(TextWriter writer1, TextWriter writer2)
        {
            w1 = writer1;
            w2 = writer2;
        }
        public override Encoding Encoding => Encoding.Default;
        public override void Write(char[] buffer, int index, int count)
        {
            OnOutput?.Invoke(this, EventArgs.Empty);
            try
            {
                w1?.Write(buffer, index, count);
            }
            catch  { }
            try
            {
                w2?.Write(buffer, index, count);
            }
            catch { }
            Flush();
        }
        public override void Flush()
        {
            try
            {
                w1?.Flush();
            }
            catch { }
            try
            {
                w2?.Flush();
            }
            catch { }
        }

        public override void WriteLine(string value)
        {
            base.WriteLine(getPrintingContext() + value);
        }

        public override void WriteLine(bool value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(char value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(char[] buffer)
        {
            base.Write(getPrintingContext());
            base.WriteLine(buffer);
        }
        public override void WriteLine(char[] buffer, int index, int count)
        {
            base.Write(getPrintingContext());
            base.WriteLine(buffer, index, count);
        }
        public override void WriteLine(decimal value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }
        public override void WriteLine(double value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(float value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(int value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(long value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(object value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(uint value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        public override void WriteLine(ulong value)
        {
            base.Write(getPrintingContext());
            base.WriteLine(value);
        }

        private string getPrintingContext()
        {
            StringBuilder result = new StringBuilder("[");
            result.Append(DateTime.Now.ToString("yyyy\\-MM\\-dd HH\\:mm\\:ss\\.fff"));
            int skipFrames = 4;
            skipMore:
            StackFrame invokerInfo = new StackTrace(skipFrames, true).GetFrame(0);
            if (invokerInfo != null)
            {
                MethodBase invoker = invokerInfo.GetMethod();
                if (invoker.DeclaringType.Namespace != null && invoker.DeclaringType.Namespace.StartsWith("System."))
                {
                    skipFrames++;
                    goto skipMore;
                }
                result.Append(" ");
                result.Append(invoker.DeclaringType.Name + "." + invoker.Name);
                result.Append($"({Path.GetFileName(invokerInfo.GetFileName())}:{invokerInfo.GetFileLineNumber()})");
            }
            return result.Append("] ").ToString();
        }
    }
}
